package cn.cleanarch.dp.gateway.system.service.impl;

import cn.cleanarch.dp.common.core.exception.enums.ErrorCodeConstants;
import cn.cleanarch.dp.common.core.exception.util.ServiceExceptionUtil;
import cn.cleanarch.dp.common.core.utils.collection.CollectionUtils;
import cn.cleanarch.dp.gateway.system.mapper.ErrorCodeMapper;
import cn.cleanarch.dp.gateway.system.service.ErrorCodeService;
import cn.cleanarch.dp.system.constants.SysErrorCodeTypeEnum;
import cn.cleanarch.dp.system.convert.ErrorCodeConvert;
import cn.cleanarch.dp.system.domain.SysErrorCodeDO;
import cn.cleanarch.dp.system.dto.SysErrorCodeAutoGenerateReqDTO;
import cn.cleanarch.dp.system.dto.SysErrorCodeRespDTO;
import cn.cleanarch.dp.system.vo.SysErrorCodeCreateReqVO;
import cn.cleanarch.dp.system.vo.SysErrorCodeExportReqVO;
import cn.cleanarch.dp.system.vo.SysErrorCodePageReqVO;
import cn.cleanarch.dp.system.vo.SysErrorCodeUpdateReqVO;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

/**
 * 错误码 Service 实现类
 *
 * @author lihaifeng
 */
@Service
@Validated
@Slf4j
public class ErrorCodeServiceImpl implements ErrorCodeService {

    @Resource
    private ErrorCodeMapper errorCodeMapper;

    @Override
    public String createErrorCode(SysErrorCodeCreateReqVO createReqVO) {
        // 校验 code 重复
        validateCodeDuplicate(createReqVO.getCode(), null);

        // 插入
        SysErrorCodeDO errorCode = ErrorCodeConvert.INSTANCE.convert(createReqVO);
        errorCode.setType(SysErrorCodeTypeEnum.MANUAL_OPERATION.getType());
        errorCodeMapper.insert(errorCode);
        // 返回
        return errorCode.getId();
    }

    @Override
    public void updateErrorCode(SysErrorCodeUpdateReqVO updateReqVO) {
        // 校验存在
        this.validateErrorCodeExists(updateReqVO.getId());
        // 校验 code 重复
        validateCodeDuplicate(updateReqVO.getCode(), updateReqVO.getId());

        // 更新
        SysErrorCodeDO updateObj = ErrorCodeConvert.INSTANCE.convert(updateReqVO);
        updateObj.setType(SysErrorCodeTypeEnum.MANUAL_OPERATION.getType());
        errorCodeMapper.updateById(updateObj);
    }

    @Override
    public void deleteErrorCode(String id) {
        // 校验存在
        this.validateErrorCodeExists(id);
        // 删除
        errorCodeMapper.deleteById(id);
    }

    /**
     * 校验错误码的唯一字段是否重复
     *
     * 是否存在相同编码的错误码
     *
     * @param code 错误码编码
     * @param id 错误码编号
     */
    @VisibleForTesting
    public void validateCodeDuplicate(Integer code, String id) {
        SysErrorCodeDO sysErrorCodeDO = errorCodeMapper.selectByCode(code);
        if (sysErrorCodeDO == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的错误码
        if (id == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ERROR_CODE_DUPLICATE);
        }
        if (!sysErrorCodeDO.getId().equals(id)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ERROR_CODE_DUPLICATE);
        }
    }

    @VisibleForTesting
    public void validateErrorCodeExists(String id) {
        if (errorCodeMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ERROR_CODE_NOT_EXISTS);
        }
    }

    @Override
    public SysErrorCodeDO getErrorCode(String id) {
        return errorCodeMapper.selectById(id);
    }

    @Override
    public IPage<SysErrorCodeDO> getErrorCodePage(SysErrorCodePageReqVO pageReqVO) {
        return errorCodeMapper.selectPage(pageReqVO);
    }

    @Override
    public List<SysErrorCodeDO> getErrorCodeList(SysErrorCodeExportReqVO exportReqVO) {
        return errorCodeMapper.selectList(exportReqVO);
    }

    @Override
    @Transactional
    public void autoGenerateErrorCodes(List<SysErrorCodeAutoGenerateReqDTO> autoGenerateDTOs) {
        if (CollUtil.isEmpty(autoGenerateDTOs)) {
            return;
        }
        // 获得错误码
        List<SysErrorCodeDO> sysErrorCodeDOS = errorCodeMapper.selectListByCodes(
                CollectionUtils.convertSet(autoGenerateDTOs, SysErrorCodeAutoGenerateReqDTO::getCode));
        Map<Integer, SysErrorCodeDO> errorCodeDOMap = CollectionUtils.convertMap(sysErrorCodeDOS, SysErrorCodeDO::getCode);

        // 遍历 autoGenerateBOs 数组，逐个插入或更新。考虑到每次量级不大，就不走批量了
        autoGenerateDTOs.forEach(autoGenerateDTO -> {
            SysErrorCodeDO sysErrorCodeDO = errorCodeDOMap.get(autoGenerateDTO.getCode());
            // 不存在，则进行新增
            if (sysErrorCodeDO == null) {
                sysErrorCodeDO = ErrorCodeConvert.INSTANCE.convert(autoGenerateDTO);
                sysErrorCodeDO.setType(SysErrorCodeTypeEnum.AUTO_GENERATION.getType());
                errorCodeMapper.insert(sysErrorCodeDO);
                return;
            }
            // 存在，则进行更新。更新有三个前置条件：
            // 条件 1. 只更新自动生成的错误码，即 Type 为 ErrorCodeTypeEnum.AUTO_GENERATION
            if (!SysErrorCodeTypeEnum.AUTO_GENERATION.getType().equals(sysErrorCodeDO.getType())) {
                return;
            }
            // 条件 2. 分组 applicationName 必须匹配，避免存在错误码冲突的情况
            if (!autoGenerateDTO.getApplicationName().equals(sysErrorCodeDO.getApplicationName())) {
                log.error("[autoGenerateErrorCodes][自动创建({}/{}) 错误码失败，数据库中已经存在({}/{})]",
                        autoGenerateDTO.getCode(), autoGenerateDTO.getApplicationName(),
                        sysErrorCodeDO.getCode(), sysErrorCodeDO.getApplicationName());
                return;
            }
            // 条件 3. 错误提示语存在差异
            if (autoGenerateDTO.getMessage().equals(sysErrorCodeDO.getMessage())) {
                return;
            }
            // 最终匹配，进行更新
            SysErrorCodeDO codeDO = new SysErrorCodeDO();
            codeDO.setId(sysErrorCodeDO.getId());
            codeDO.setMessage(autoGenerateDTO.getMessage());
            errorCodeMapper.updateById(codeDO);
        });
    }

    @Override
    public List<SysErrorCodeRespDTO> getErrorCodeList(String applicationName, LocalDateTime minUpdateTime) {
        List<SysErrorCodeDO> sysErrorCodeDOS = errorCodeMapper.selectListByApplicationNameAndUpdateTimeGt(
                applicationName, minUpdateTime);
        return ErrorCodeConvert.INSTANCE.convertList03(sysErrorCodeDOS);
    }

}

